﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Microsoft.Forums.Tests.HelperTools.Stubs;
using Microsoft.Forums.ForumsClient.Models;
using Microsoft.Forums.Tests.HelperTools;
using Microsoft.Forums.ForumsProvider;
using Microsoft.Forums.OfflineStore;
using System.Threading;
using System.ComponentModel;
using System.Windows.Threading;

namespace ForumsClientUnitTests
{
    /// <summary>
    /// Test the ForumContainer class
    /// </summary>
    [TestFixture]
    public class SynchronizationManagerTests
    {
        // Stubs
        SyncControllerStub syncStub;
        ForumsProviderStub providerStub;
        SubscribedForumsCollection subscribedForums;
        ConnectionStub connectionStub;

        // Reset event for testing asynchronous parts
        ManualResetEvent resetEvent;

        /// <summary>
        /// Initialized the stub provider and stub connection.
        /// </summary>
        [TestFixtureSetUp]
        public void SetupFixture()
        {
            DatabaseHelper.SetupDatabase();
        }


        /// <summary>
        /// Initialize the stubs
        /// </summary>
        [SetUp]
        public void Setup()
        {
            syncStub = new SyncControllerStub();
            providerStub = new ForumsProviderStub();
            providerStub.ProviderId_Result = Guid.NewGuid();
            providerStub.ProviderName_Result = "SomeProvider";
            subscribedForums = new SubscribedForumsCollection();
            connectionStub = new ConnectionStub();
            connectionStub.IsConnected_Result = true;
            
            AppDataModel.Initialize(providerStub, null, connectionStub, DatabaseHelper.DatabaseFileName, subscribedForums);
            AppDataModel.Instance.CurrentUser.LogOnState = LogOnStatus.Authenticated;
            resetEvent = new ManualResetEvent(false);
        }

        /// <summary>
        /// After test is complete remove all data we added from the database
        /// 
        /// Ideally, we would just be able to recreate the database but there is an odd 
        /// file locked issue that happens intermittently
        /// </summary
        [TearDown]
        public void Teardown()
        {
            DatabaseHelper.ClearDatabase();
        }

        /// <summary>
        /// Creates the synchronization manager
        /// </summary>
        /// <returns></returns>
        private SynchronizationManager CreateSynchronizationManager()
        {
            SynchronizationManager manager =
                new SynchronizationManager();

            return manager;
        }

        /// <summary>
        /// Creates the synchronization manager with Controller stub
        /// </summary>
        /// <returns></returns>
        private SynchronizationManager CreateSynchronizationManagerForSchedulerTests()
        {
            SynchronizationManager manager = new SynchronizationManager(syncStub);
            manager.SchedulerRunCompleted += SignalResetOnEvent();
            manager.SyncFrequency = 60 * 60; // an hour
            manager.SchedulerStartDelay = 0;
            manager.SchedulerPeriod = 0;//Scheduler will only run once
            return manager;
        }

        /// <summary>
        /// Signals the reset on event
        /// </summary>
        /// <returns></returns>
        private EventHandler<EventArgs> SignalResetOnEvent()
        {
            return delegate
            {
                    resetEvent.Set();
            };
        }


        /// <summary>
        /// Generates the test forums.
        /// SOme tests depend on this data
        /// </summary>
        /// <param name="now">The now.</param>
        /// <param name="yesterday">The yesterday.</param>
        private void GenerateTestForums(DateTime? now, DateTime? yesterday)
        {
            Forum forum = ForumObjectHelper.MakeNewForum("A", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = yesterday;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

            forum = ForumObjectHelper.MakeNewForum("B", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = null;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

            forum = ForumObjectHelper.MakeNewForum("C", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = now;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

            forum = ForumObjectHelper.MakeNewForum("D", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = now;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

            forum = ForumObjectHelper.MakeNewForum("E", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = yesterday;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

            forum = ForumObjectHelper.MakeNewForum("F", providerStub.ProviderId);
            forum.ForumPropertySet.LastSyncedOn = null;
            subscribedForums.Add(new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher));

        }


        #region Controller Property Tests
        /// <summary>
        /// Controller property does not return null.
        /// </summary>
        [Test]
        public void ControllerPropertyDoesNotReturnNull()
        {
            SynchronizationManager manager = CreateSynchronizationManager();

            Assert.IsNotNull(manager.Controller);
        }
        #endregion

        #region AddForumToScheduler tests

        /// <summary>
        /// AddForumToScheduler throws if forum container is null.
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddForumToSchedulerThrowsIfForumContainerIsNull()
        {
            SynchronizationManager manager = CreateSynchronizationManager();

            manager.AddForumToScheduler(null);
        }

        /// <summary>
        /// AddForumToScheduler throws if forum container has already been added
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void AddForumToSchedulerThrowsIfForumContainerHasAlreadyBeenAdded()
        {
            ForumDataCreator creator = new ForumDataCreator(DatabaseHelper.GetNewDataContext());
            creator.Load("RootPostTest.xml");
            ForumsOfflineStore db = DatabaseHelper.GetNewDataContext();
            Forum forum = db.Forums.Single(f => f.DisplayName == "Forum1");

            SynchronizationManager manager = CreateSynchronizationManager();
            ForumFolderViewModel fc = new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher);

            manager.AddForumToScheduler(fc);
            manager.AddForumToScheduler(fc);
        }

        #endregion

        #region RemoveForumFromScheduler tests

        /// <summary>
        /// RemoveForumFromScheduler throws if forum container is null.
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void RemoveForumFromSchedulerThrowsIfForumContainerIsNull()
        {
            SynchronizationManager manager = CreateSynchronizationManager();

            manager.RemoveForumFromScheduler(null);
        }

        /// <summary>
        /// RemoveForumFromScheduler throws if forum container does not exist
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void RemoveForumFromSchedulerThrowsIfForumContainerIsNotInScheduler()
        {
            var creator = new ForumDataCreator(DatabaseHelper.GetNewDataContext());
            creator.Load("RootPostTest.xml");
            var db = DatabaseHelper.GetNewDataContext();
            var forum = db.Forums.Single(f => f.DisplayName == "Forum1");
            SynchronizationManager manager = CreateSynchronizationManager();

            ForumFolderViewModel fc = new ForumFolderViewModel(forum, Dispatcher.CurrentDispatcher);

            manager.RemoveForumFromScheduler(fc);
        }

        #endregion

        #region SchedulerTests

        /// <summary>
        /// Test that the Scheduler the chooses never before synced forums first
        /// </summary>
        [Test]
        public void SchedulerSynchronizesNeverBeforeSynchornizedForumsFirst()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = 2;
            foreach (var f in subscribedForums)
            {
                manager.AddForumToScheduler(f);
            }

            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000*60,false); // make sure test executes in under a minute

            Assert.AreEqual(2,subscribedForums.Count(fc => fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "B" && fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "F" && fc.IsSyncInProgress));
        }


        /// <summary>
        /// Test that the Scheduler chooses only the forums that are due for syncing bases upon the sync frequency
        /// </summary>
        [Test]
        public void SchedulerSynchronizesForumsBasesOnSyncFrequency()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = subscribedForums.Count;
            foreach (var f in subscribedForums)
            {
                manager.AddForumToScheduler(f);
            }

            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000 * 60, false); // make sure test executes in under a minute

            Assert.AreEqual(4, subscribedForums.Count(fc => fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "A" && fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "B" && fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "E" && fc.IsSyncInProgress));
            Assert.IsTrue(subscribedForums.Any(fc => fc.Forum.Name == "F" && fc.IsSyncInProgress));

        }

        /// <summary>
        /// Test that the Scheduler synchronizes nothing when last sync date is too recent
        /// </summary>
        [Test]
        public void SchedulerSynchronizesNothingWhenLastSyncDateIsTooRecent()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = subscribedForums.Count;
            foreach (var f in subscribedForums)
            {
                f.Forum.ForumPropertySet.LastSyncedOn = DateTime.Now.ToUniversalTime();
                manager.AddForumToScheduler(f);
            }

            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000 * 60, false); // make sure test executes in under a minute

            Assert.AreEqual(0, subscribedForums.Count(fc => fc.IsSyncInProgress));

        }

        /// <summary>
        /// Test that the Scheduler does not synchronize more than the SyncLimit
        /// </summary>
        [Test]
        public void SchedulerSynchronizesDoesNotSynchronizeMoreThanSyncLimit()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = 2;
            manager.SchedulerPeriod = 0;//Scheduler will only run once
            foreach (var f in subscribedForums)
            {
                manager.AddForumToScheduler(f);
            }

            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000 * 60, false); // make sure test executes in under a minute

            Assert.AreEqual(2, subscribedForums.Count(fc => fc.IsSyncInProgress));

        }

        /// <summary>
        /// Test that the Scheduler synchronizes nothing when user is not logged in
        /// </summary>
        [Test]
        public void SchedulerSynchronizesNothingWhenUserIsNotLoggedIn()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = subscribedForums.Count;
            foreach (var f in subscribedForums)
            {
                manager.AddForumToScheduler(f);
            }

            AppDataModel.Instance.CurrentUser.LogOnState = LogOnStatus.Offline;
            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000 * 60, false); // make sure test executes in under a minute

            Assert.AreEqual(0, subscribedForums.Count(fc => fc.IsSyncInProgress));

        }

        /// <summary>
        /// Test that the Scheduler synchronizes nothing when application is offline
        /// </summary>
        [Test]
        public void SchedulerSynchronizesNothingWhenApplicationIsOffline()
        {
            DateTime? now = DateTime.Now.ToUniversalTime();
            DateTime? yesterday = DateTime.Now.AddDays(-1).ToUniversalTime();
            GenerateTestForums(now, yesterday);
            SynchronizationManager manager = CreateSynchronizationManagerForSchedulerTests();
            manager.SchedulerSynchronizationLimit = subscribedForums.Count;
            foreach (var f in subscribedForums)
            {
                manager.AddForumToScheduler(f);
            }

            connectionStub.IsConnected_Result = false;
            manager.IsSchedulerEnabled = true;
            resetEvent.WaitOne(1000 * 60, false); // make sure test executes in under a minute

            Assert.AreEqual(0, subscribedForums.Count(fc => fc.IsSyncInProgress));

        }
        #endregion
    }
}
